package template;


import java.util.Random;

import logist.simulation.Vehicle;
import logist.agent.Agent;
import logist.behavior.ReactiveBehavior;
import logist.plan.Action;
import logist.plan.Action.Move;
import logist.plan.Action.Pickup;
import logist.task.Task;
import logist.task.TaskDistribution;
import logist.topology.Topology;
import logist.topology.Topology.City;

public class ReactiveTemplate implements ReactiveBehavior {

	private Random random;

	
	//the bestAction is the table that contains
	//the best action corresponds to given state 
	private int[] bestAction;
	
	//number of cities in the map
	private int cityNum;

	//number of all possible states
	private int stateNum;
	
	private static double costPerKM = 5;
	private static boolean debugMode = false;
	
	@Override
	public void setup(Topology topology, TaskDistribution td, Agent agent) {

		// Reads the discount factor from the agents.xml file.
		// If the property is not present it defaults to 0.95
		Double discount = agent.readProperty("discount-factor", Double.class,
				0.95);
		
		cityNum = topology.size();
		
		// state num i*cityNum + j represents:
		// - state where car is at the city i and have task to city j  if i<>j
		// - state where car is at the city i and have no tasks        if i==j
		stateNum = cityNum*cityNum;
		
		double[] V = new double[stateNum];
		
		bestAction = new int[stateNum];
		
		// rewards defines the reward from one state to another 
		// rewards[][0] is the reward if the action is pick_up
		// rewards[][1] is the reward if the action is refuse
		double[][] rewards = new double[stateNum][2];
		

		// probas is the table that saves all the probability 
		// to pass from one state to another state
		// probas[][0][] represents the probability by choosing action pick_up
		// probas[][1][] represents the probability by choosing action refuse
		double[][][] probas = new double[stateNum][2][stateNum];


		int i, j, k;
		
		for (City city_i : topology) {
			i = city_i.id;
			for (City city_j : topology) {
				j = city_j.id;

				double temp = 0.0;
				for (City ngb : city_i.neighbors()) {
					temp += city_i.distanceTo(ngb);
				}

				
				if (i != j) { //case where task exists at current city i
					
					//we define reward as reward per kilometer - cost per kilometer
					//if the strategy is pick_up
					rewards[i*cityNum+j][0] = td.reward(city_i, city_j)/city_i.distanceTo(city_j) - costPerKM  ;
					
					//if the strategy is refuse
					rewards[i*cityNum+j][1] = - costPerKM ;
					
					temp = 0.0;
					for (City city_k : topology) {
						k = city_k.id;
						if (j != k) {
							probas[i*cityNum+j][0][j*cityNum+k] = td.probability(city_j, city_k);
							temp += td.probability(city_j, city_k);
						}
					}
					
					//probability of having no tasks at city j
					probas[i*cityNum+j][0][j*cityNum+j] = 1 - temp; 

					//case when refuse
					for (City ngb : city_i.neighbors()) {
						int ngbId = ngb.id;
						temp = 0.0;
						for (City city_k : topology) {
							k = city_k.id;
							if (ngbId != k) {
								probas[i*cityNum+j][1][ngbId*cityNum+k] = td.probability(ngb, city_k)/city_i.neighbors().size();
								temp += td.probability(ngb, city_k);
							}
						}
						probas[i*cityNum+j][1][ngbId*cityNum+ngbId] = (1 - temp)/city_i.neighbors().size();
					}
					
				} else { //case where no task exist at current city i... have to do a random move
					rewards[i*cityNum+j][0] = - costPerKM ;
					rewards[i*cityNum+j][1] = - costPerKM ;
					
					for (City ngb : city_i.neighbors()) {
						int ngbId = ngb.id;
						temp = 0.0;
						for (City city_k : topology) {
							k = city_k.id;
							if (ngbId != k) {
								probas[i*cityNum+j][0][ngbId*cityNum+k] = td.probability(ngb, city_k)/city_i.neighbors().size();
								probas[i*cityNum+j][1][ngbId*cityNum+k] = td.probability(ngb, city_k)/city_i.neighbors().size();
								temp += td.probability(ngb, city_k);
							}
						}
						probas[i*cityNum+j][0][ngbId*cityNum+ngbId] = (1 - temp)/city_i.neighbors().size();
						probas[i*cityNum+j][1][ngbId*cityNum+ngbId] = (1 - temp)/city_i.neighbors().size();
					}

				}
			}
		}

		
		double[][] Q = new double[stateNum][2] ;
		
		double loopStopPoint = 0.000001;
		
		double maxDiff = -1;
		do {
			maxDiff = -1;
			for (int l = 0; l < stateNum; l++) {
				double tmp = 0.0;
				for (int m = 0; m < stateNum; m++){
					tmp += probas[l][0][m]*V[m];
				}
				Q[l][0] = rewards[l][0] + discount* tmp;
				
				tmp = 0.0;
				for (int m = 0; m < stateNum; m++){
					tmp += probas[l][1][m]*V[m];
				}
				Q[l][1] = rewards[l][1] + discount* tmp;
				
				tmp = V[l];

				// we take V[l] and bestAction[l] as the highest one with best strategy
				if ( Q[l][0] >= Q[l][1] ) {
					V[l] = Q[l][0];
					bestAction[l] = 0;
				} else {
					V[l] = Q[l][1];
					bestAction[l] = 1;
				}
				
				// we take MAX(V, V')
				if ( Math.abs(V[l]-tmp) > maxDiff) {
					maxDiff = Math.abs(V[l]-tmp);
				}
			}
			
			if (debugMode) {
				System.out.println("maxDiff = " + maxDiff);
			}
		} while (maxDiff > loopStopPoint);
		
		this.random = new Random(); 
		
	}
	

	@Override
	public Action act(Vehicle vehicle, Task availableTask) {
		Action action;

		if (availableTask == null ) { // case where no available task in the city ... random move
			City currentCity = vehicle.getCurrentCity();
			action = new Move(currentCity.randomNeighbor(random));
			if (debugMode) {
				System.out.println("no task");
			}
		} else { // case task available 
			int currentCityId = vehicle.getCurrentCity().id;
			int deliveryCityId = availableTask.deliveryCity.id;
			if (bestAction[currentCityId*cityNum+deliveryCityId] == 0) { //case best strategy is pick_up
				action = new Pickup(availableTask);
				if (debugMode) {
					System.out.println("pick up");
				}
			} else { //case best strategy is random move
				City currentCity = vehicle.getCurrentCity();
				action = new Move(currentCity.randomNeighbor(random));
				if (debugMode) {
					System.out.println("refuse");
				}
			}
		}

		
		return action;
	}
}
